Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.55% covered (success)
91.55%
65 / 71
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetadataFactory
91.55% covered (success)
91.55%
65 / 71
25.00% covered (danger)
25.00%
2 / 8
31.58
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMetadataStrategy
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
12
 getScalarForType
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getMetadataStrategyForType
96.55% covered (success)
96.55%
28 / 29
0.00% covered (danger)
0.00%
0 / 1
8
 getMethodMetadata
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCreationMetadata
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getModificationMetadata
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getResultMetadata
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2namespace Apie\Core\Metadata;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Context\MetadataFieldHashmap;
6use Apie\Core\Enums\ScalarType;
7use Apie\Core\Exceptions\InvalidTypeException;
8use Apie\Core\Metadata\Fields\ConstructorParameter;
9use Apie\Core\Metadata\Strategy\BuiltInPhpClassStrategy;
10use Apie\Core\Metadata\Strategy\CompositeValueObjectStrategy;
11use Apie\Core\Metadata\Strategy\DtoStrategy;
12use Apie\Core\Metadata\Strategy\EnumStrategy;
13use Apie\Core\Metadata\Strategy\ExceptionStrategy;
14use Apie\Core\Metadata\Strategy\ItemHashmapStrategy;
15use Apie\Core\Metadata\Strategy\ItemListObjectStrategy;
16use Apie\Core\Metadata\Strategy\PolymorphicEntityStrategy;
17use Apie\Core\Metadata\Strategy\RegularObjectStrategy;
18use Apie\Core\Metadata\Strategy\ScalarStrategy;
19use Apie\Core\Metadata\Strategy\UnionTypeStrategy;
20use Apie\Core\Metadata\Strategy\ValueObjectStrategy;
21use LogicException;
22use ReflectionClass;
23use ReflectionIntersectionType;
24use ReflectionMethod;
25use ReflectionNamedType;
26use ReflectionType;
27use ReflectionUnionType;
28
29final class MetadataFactory
30{
31    private function __construct()
32    {
33    }
34
35    /**
36     * @param ReflectionClass<object> $class
37     */
38    public static function getMetadataStrategy(ReflectionClass $class): StrategyInterface
39    {
40        if (BuiltInPhpClassStrategy::supports($class)) {
41            return new BuiltInPhpClassStrategy($class);
42        }
43        if (ScalarStrategy::supports($class)) {
44            return new ScalarStrategy(ScalarType::STDCLASS);
45        }
46        if (EnumStrategy::supports($class)) {
47            return new EnumStrategy($class);
48        }
49        if (PolymorphicEntityStrategy::supports($class)) {
50            return new PolymorphicEntityStrategy($class);
51        }
52        if (CompositeValueObjectStrategy::supports($class)) {
53            return new CompositeValueObjectStrategy($class);
54        }
55        if (ItemListObjectStrategy::supports($class)) {
56            return new ItemListObjectStrategy($class);
57        }
58        if (ItemHashmapStrategy::supports($class)) {
59            return new ItemHashmapStrategy($class);
60        }
61        if (DtoStrategy::supports($class)) {
62            return new DtoStrategy($class);
63        }
64        if (ValueObjectStrategy::supports($class)) {
65            return new ValueObjectStrategy($class);
66        }
67        if (ExceptionStrategy::supports($class)) {
68            return new ExceptionStrategy($class);
69        }
70        if (RegularObjectStrategy::supports($class)) {
71            return new RegularObjectStrategy($class);
72        }
73
74        throw new InvalidTypeException($class->name, 'Apie supported object');
75    }
76
77    public static function getScalarForType(?ReflectionType $typehint, bool $nullable = true): ScalarType
78    {
79        if ($typehint === null) {
80            return ScalarType::MIXED;
81        }
82        return self::getMetadataStrategyForType($typehint)
83            ->getResultMetadata(new ApieContext())
84            ->toScalarType($nullable);
85    }
86
87    public static function getMetadataStrategyForType(ReflectionType $typehint): StrategyInterface
88    {
89        if ($typehint instanceof ReflectionUnionType) {
90            $metadata = [];
91            foreach ($typehint->getTypes() as $type) {
92                $metadata[] = self::getMetadataStrategyForType($type)->getCreationMetadata(new ApieContext());
93            }
94            return new UnionTypeStrategy(...$metadata);
95        }
96        if ($typehint instanceof ReflectionIntersectionType) {
97            throw new LogicException('Intersection typehints are not supported yet');
98        }
99        assert($typehint instanceof ReflectionNamedType);
100        if ($typehint->isBuiltin()) {
101            if ($typehint->getName() === 'null') {
102                return new ScalarStrategy(ScalarType::NULLVALUE);
103            }
104            if ($typehint->getName() === 'mixed') {
105                return new ScalarStrategy(ScalarType::MIXED);
106            }
107            $strategy = new ScalarStrategy(
108                match ($typehint->getName()) {
109                    'string' => ScalarType::STRING,
110                    'float' => ScalarType::FLOAT,
111                    'int' => ScalarType::INTEGER,
112                    'array' => ScalarType::ARRAY,
113                    'mixed' => ScalarType::MIXED,
114                    'bool' => ScalarType::BOOLEAN,
115                    'true' => ScalarType::BOOLEAN,
116                    'false' => ScalarType::BOOLEAN,
117                    default => throw new InvalidTypeException($typehint->getName(), 'string|float|int|null|array|mixed|bool')
118                }
119            );
120        } else {
121            $strategy = self::getMetadataStrategy(new ReflectionClass($typehint->getName()));
122        }
123        if ($typehint->allowsNull()) {
124            return new UnionTypeStrategy($strategy, new ScalarMetadata(ScalarType::NULLVALUE));
125        }
126
127        return $strategy;
128    }
129
130    public static function getMethodMetadata(ReflectionMethod $method, ApieContext $context): MetadataInterface
131    {
132        $fields = [];
133        foreach ($method->getParameters() as $parameter) {
134            $fields[$parameter->name] = new ConstructorParameter($parameter);
135        }
136        return new CompositeMetadata(new MetadataFieldHashmap($fields));
137    }
138
139    /**
140     * @param ReflectionClass<object>|ReflectionType $typehint
141     */
142    public static function getCreationMetadata(ReflectionClass|ReflectionType $typehint, ApieContext $context): MetadataInterface
143    {
144        if ($typehint instanceof ReflectionType) {
145            return self::getMetadataStrategyForType($typehint)->getCreationMetadata($context);
146        }
147        return self::getMetadataStrategy($typehint)->getCreationMetadata($context);
148    }
149
150    /**
151     * @param ReflectionClass<object>|ReflectionType $typehint
152     */
153    public static function getModificationMetadata(ReflectionClass|ReflectionType $typehint, ApieContext $context): MetadataInterface
154    {
155        if ($typehint instanceof ReflectionType) {
156            return self::getMetadataStrategyForType($typehint)->getModificationMetadata($context);
157        }
158        return self::getMetadataStrategy($typehint)->getModificationMetadata($context);
159    }
160
161    /**
162     * @param ReflectionClass<object>|ReflectionType $typehint
163     */
164    public static function getResultMetadata(ReflectionClass|ReflectionType $typehint, ApieContext $context): MetadataInterface
165    {
166        if ($typehint instanceof ReflectionType) {
167            return self::getMetadataStrategyForType($typehint)->getResultMetadata($context);
168        }
169        return self::getMetadataStrategy($typehint)->getResultMetadata($context);
170    }
171}